1   /*
2    * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  
27  package sun.util.logging;
28  
29  import java.lang.ref.WeakReference;
30  import java.lang.reflect.InvocationTargetException;
31  import java.lang.reflect.Method;
32  import java.io.PrintStream;
33  import java.io.PrintWriter;
34  import java.io.StringWriter;
35  import java.security.AccessController;
36  import java.security.PrivilegedAction;
37  import java.text.MessageFormat;
38  import java.util.Date;
39  import java.util.HashMap;
40  import java.util.Map;
41  import sun.misc.JavaLangAccess;
42  import sun.misc.SharedSecrets;
43  
44  /**
45   * Platform logger provides an API for the JRE components to log
46   * messages.  This enables the runtime components to eliminate the
47   * static dependency of the logging facility and also defers the
48   * java.util.logging initialization until it is enabled.
49   * In addition, the PlatformLogger API can be used if the logging
50   * module does not exist.
51   *
52   * If the logging facility is not enabled, the platform loggers
53   * will output log messages per the default logging configuration
54   * (see below). In this implementation, it does not log the
55   * the stack frame information issuing the log message.
56   *
57   * When the logging facility is enabled (at startup or runtime),
58   * the java.util.logging.Logger will be created for each platform
59   * logger and all log messages will be forwarded to the Logger
60   * to handle.
61   *
62   * Logging facility is "enabled" when one of the following
63   * conditions is met:
64   * 1) a system property "java.util.logging.config.class" or
65   *    "java.util.logging.config.file" is set
66   * 2) java.util.logging.LogManager or java.util.logging.Logger
67   *    is referenced that will trigger the logging initialization.
68   *
69   * Default logging configuration:
70   *   global logging level = INFO
71   *   handlers = java.util.logging.ConsoleHandler
72   *   java.util.logging.ConsoleHandler.level = INFO
73   *   java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
74   *
75   * Limitation:
76   * <JAVA_HOME>/lib/logging.properties is the system-wide logging
77   * configuration defined in the specification and read in the
78   * default case to configure any java.util.logging.Logger instances.
79   * Platform loggers will not detect if <JAVA_HOME>/lib/logging.properties
80   * is modified. In other words, unless the java.util.logging API
81   * is used at runtime or the logging system properties is set,
82   * the platform loggers will use the default setting described above.
83   * The platform loggers are designed for JDK developers use and
84   * this limitation can be workaround with setting
85   * -Djava.util.logging.config.file system property.
86   *
87   * @since 1.7
88   */
89  public class PlatformLogger {
90      // Same values as java.util.logging.Level for easy mapping
91      public static final int OFF     = Integer.MAX_VALUE;
92      public static final int SEVERE  = 1000;
93      public static final int WARNING = 900;
94      public static final int INFO    = 800;
95      public static final int CONFIG  = 700;
96      public static final int FINE    = 500;
97      public static final int FINER   = 400;
98      public static final int FINEST  = 300;
99      public static final int ALL     = Integer.MIN_VALUE;
100 
101     private static final int defaultLevel = INFO;
102     private static boolean loggingEnabled;
103     static {
104         loggingEnabled = AccessController.doPrivileged(
105             new PrivilegedAction<Boolean>() {
106                 public Boolean run() {
107                     String cname = System.getProperty("java.util.logging.config.class");
108                     String fname = System.getProperty("java.util.logging.config.file");
109                     return (cname != null || fname != null);
110                 }
111             });
112     }
113 
114     // Table of known loggers.  Maps names to PlatformLoggers.
115     private static Map<String,WeakReference<PlatformLogger>> loggers =
116         new HashMap<>();
117 
118     /**
119      * Returns a PlatformLogger of a given name.
120      */
121     public static synchronized PlatformLogger getLogger(String name) {
122         PlatformLogger log = null;
123         WeakReference<PlatformLogger> ref = loggers.get(name);
124         if (ref != null) {
125             log = ref.get();
126         }
127         if (log == null) {
128             log = new PlatformLogger(name);
129             loggers.put(name, new WeakReference<>(log));
130         }
131         return log;
132     }
133 
134     /**
135      * Initialize java.util.logging.Logger objects for all platform loggers.
136      * This method is called from LogManager.readPrimordialConfiguration().
137      */
138     public static synchronized void redirectPlatformLoggers() {
139         if (loggingEnabled || !LoggingSupport.isAvailable()) return;
140 
141         loggingEnabled = true;
142         for (Map.Entry<String, WeakReference<PlatformLogger>> entry : loggers.entrySet()) {
143             WeakReference<PlatformLogger> ref = entry.getValue();
144             PlatformLogger plog = ref.get();
145             if (plog != null) {
146                 plog.newJavaLogger();
147             }
148         }
149     }
150 
151     /**
152      * Creates a new JavaLogger that the platform logger uses
153      */
154     private void newJavaLogger() {
155         logger = new JavaLogger(logger.name, logger.effectiveLevel);
156     }
157 
158     // logger may be replaced with a JavaLogger object
159     // when the logging facility is enabled
160     private volatile LoggerProxy logger;
161 
162     private PlatformLogger(String name) {
163         if (loggingEnabled) {
164             this.logger = new JavaLogger(name);
165         } else {
166             this.logger = new LoggerProxy(name);
167         }
168     }
169 
170     /**
171      * A convenience method to test if the logger is turned off.
172      * (i.e. its level is OFF).
173      */
174     public boolean isEnabled() {
175         return logger.isEnabled();
176     }
177 
178     /**
179      * Gets the name for this platform logger.
180      */
181     public String getName() {
182         return logger.name;
183     }
184 
185     /**
186      * Returns true if a message of the given level would actually
187      * be logged by this logger.
188      */
189     public boolean isLoggable(int level) {
190         return logger.isLoggable(level);
191     }
192 
193     /**
194      * Gets the current log level.  Returns 0 if the current effective level
195      * is not set (equivalent to Logger.getLevel() returns null).
196      */
197     public int getLevel() {
198         return logger.getLevel();
199     }
200 
201     /**
202      * Sets the log level.
203      */
204     public void setLevel(int newLevel) {
205         logger.setLevel(newLevel);
206     }
207 
208     /**
209      * Logs a SEVERE message.
210      */
211     public void severe(String msg) {
212         logger.doLog(SEVERE, msg);
213     }
214 
215     public void severe(String msg, Throwable t) {
216         logger.doLog(SEVERE, msg, t);
217     }
218 
219     public void severe(String msg, Object... params) {
220         logger.doLog(SEVERE, msg, params);
221     }
222 
223     /**
224      * Logs a WARNING message.
225      */
226     public void warning(String msg) {
227         logger.doLog(WARNING, msg);
228     }
229 
230     public void warning(String msg, Throwable t) {
231         logger.doLog(WARNING, msg, t);
232     }
233 
234     public void warning(String msg, Object... params) {
235         logger.doLog(WARNING, msg, params);
236     }
237 
238     /**
239      * Logs an INFO message.
240      */
241     public void info(String msg) {
242         logger.doLog(INFO, msg);
243     }
244 
245     public void info(String msg, Throwable t) {
246         logger.doLog(INFO, msg, t);
247     }
248 
249     public void info(String msg, Object... params) {
250         logger.doLog(INFO, msg, params);
251     }
252 
253     /**
254      * Logs a CONFIG message.
255      */
256     public void config(String msg) {
257         logger.doLog(CONFIG, msg);
258     }
259 
260     public void config(String msg, Throwable t) {
261         logger.doLog(CONFIG, msg, t);
262     }
263 
264     public void config(String msg, Object... params) {
265         logger.doLog(CONFIG, msg, params);
266     }
267 
268     /**
269      * Logs a FINE message.
270      */
271     public void fine(String msg) {
272         logger.doLog(FINE, msg);
273     }
274 
275     public void fine(String msg, Throwable t) {
276         logger.doLog(FINE, msg, t);
277     }
278 
279     public void fine(String msg, Object... params) {
280         logger.doLog(FINE, msg, params);
281     }
282 
283     /**
284      * Logs a FINER message.
285      */
286     public void finer(String msg) {
287         logger.doLog(FINER, msg);
288     }
289 
290     public void finer(String msg, Throwable t) {
291         logger.doLog(FINER, msg, t);
292     }
293 
294     public void finer(String msg, Object... params) {
295         logger.doLog(FINER, msg, params);
296     }
297 
298     /**
299      * Logs a FINEST message.
300      */
301     public void finest(String msg) {
302         logger.doLog(FINEST, msg);
303     }
304 
305     public void finest(String msg, Throwable t) {
306         logger.doLog(FINEST, msg, t);
307     }
308 
309     public void finest(String msg, Object... params) {
310         logger.doLog(FINEST, msg, params);
311     }
312 
313     /**
314      * Default platform logging support - output messages to
315      * System.err - equivalent to ConsoleHandler with SimpleFormatter.
316      */
317     static class LoggerProxy {
318         private static final PrintStream defaultStream = System.err;
319 
320         final String name;
321         volatile int levelValue;
322         volatile int effectiveLevel = 0; // current effective level value
323 
324         LoggerProxy(String name) {
325             this(name, defaultLevel);
326         }
327 
328         LoggerProxy(String name, int level) {
329             this.name = name;
330             this.levelValue = level == 0 ? defaultLevel : level;
331         }
332 
333         boolean isEnabled() {
334             return levelValue != OFF;
335         }
336 
337         int getLevel() {
338             return effectiveLevel;
339         }
340 
341         void setLevel(int newLevel) {
342             levelValue = newLevel;
343             effectiveLevel = newLevel;
344         }
345 
346         void doLog(int level, String msg) {
347             if (level < levelValue || levelValue == OFF) {
348                 return;
349             }
350             defaultStream.print(format(level, msg, null));
351         }
352 
353         void doLog(int level, String msg, Throwable thrown) {
354             if (level < levelValue || levelValue == OFF) {
355                 return;
356             }
357             defaultStream.print(format(level, msg, thrown));
358         }
359 
360         void doLog(int level, String msg, Object... params) {
361             if (level < levelValue || levelValue == OFF) {
362                 return;
363             }
364             String newMsg = formatMessage(msg, params);
365             defaultStream.print(format(level, newMsg, null));
366         }
367 
368         public boolean isLoggable(int level) {
369             if (level < levelValue || levelValue == OFF) {
370                 return false;
371             }
372             return true;
373         }
374 
375         // Copied from java.util.logging.Formatter.formatMessage
376         private String formatMessage(String format, Object... parameters) {
377             // Do the formatting.
378             try {
379                 if (parameters == null || parameters.length == 0) {
380                     // No parameters.  Just return format string.
381                     return format;
382                 }
383                 // Is it a java.text style format?
384                 // Ideally we could match with
385                 // Pattern.compile("\\{\\d").matcher(format).find())
386                 // However the cost is 14% higher, so we cheaply check for
387                 // 1 of the first 4 parameters
388                 if (format.indexOf("{0") >= 0 || format.indexOf("{1") >=0 ||
389                             format.indexOf("{2") >=0|| format.indexOf("{3") >=0) {
390                     return java.text.MessageFormat.format(format, parameters);
391                 }
392                 return format;
393             } catch (Exception ex) {
394                 // Formatting failed: use format string.
395                 return format;
396             }
397         }
398 
399         private static final String formatString =
400             LoggingSupport.getSimpleFormat(false); // don't check logging.properties
401 
402         // minimize memory allocation
403         private Date date = new Date();
404         private synchronized String format(int level, String msg, Throwable thrown) {
405             date.setTime(System.currentTimeMillis());
406             String throwable = "";
407             if (thrown != null) {
408                 StringWriter sw = new StringWriter();
409                 PrintWriter pw = new PrintWriter(sw);
410                 pw.println();
411                 thrown.printStackTrace(pw);
412                 pw.close();
413                 throwable = sw.toString();
414             }
415 
416             return String.format(formatString,
417                                  date,
418                                  getCallerInfo(),
419                                  name,
420                                  PlatformLogger.getLevelName(level),
421                                  msg,
422                                  throwable);
423         }
424 
425         // Returns the caller's class and method's name; best effort
426         // if cannot infer, return the logger's name.
427         private String getCallerInfo() {
428             String sourceClassName = null;
429             String sourceMethodName = null;
430 
431             JavaLangAccess access = SharedSecrets.getJavaLangAccess();
432             Throwable throwable = new Throwable();
433             int depth = access.getStackTraceDepth(throwable);
434 
435             String logClassName = "sun.util.logging.PlatformLogger";
436             boolean lookingForLogger = true;
437             for (int ix = 0; ix < depth; ix++) {
438                 // Calling getStackTraceElement directly prevents the VM
439                 // from paying the cost of building the entire stack frame.
440                 StackTraceElement frame =
441                     access.getStackTraceElement(throwable, ix);
442                 String cname = frame.getClassName();
443                 if (lookingForLogger) {
444                     // Skip all frames until we have found the first logger frame.
445                     if (cname.equals(logClassName)) {
446                         lookingForLogger = false;
447                     }
448                 } else {
449                     if (!cname.equals(logClassName)) {
450                         // We've found the relevant frame.
451                         sourceClassName = cname;
452                         sourceMethodName = frame.getMethodName();
453                         break;
454                     }
455                 }
456             }
457 
458             if (sourceClassName != null) {
459                 return sourceClassName + " " + sourceMethodName;
460             } else {
461                 return name;
462             }
463         }
464     }
465 
466     /**
467      * JavaLogger forwards all the calls to its corresponding
468      * java.util.logging.Logger object.
469      */
470     static class JavaLogger extends LoggerProxy {
471         private static final Map<Integer, Object> levelObjects =
472             new HashMap<>();
473 
474         static {
475             if (LoggingSupport.isAvailable()) {
476                 // initialize the map to Level objects
477                 getLevelObjects();
478             }
479         }
480 
481         private static void getLevelObjects() {
482             // get all java.util.logging.Level objects
483             int[] levelArray = new int[] {OFF, SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST, ALL};
484             for (int l : levelArray) {
485                 Object level = LoggingSupport.parseLevel(getLevelName(l));
486                 levelObjects.put(l, level);
487             }
488         }
489 
490         private final Object javaLogger;
491         JavaLogger(String name) {
492             this(name, 0);
493         }
494 
495         JavaLogger(String name, int level) {
496             super(name, level);
497             this.javaLogger = LoggingSupport.getLogger(name);
498             if (level != 0) {
499                 // level has been updated and so set the Logger's level
500                 LoggingSupport.setLevel(javaLogger, levelObjects.get(level));
501             }
502         }
503 
504        /**
505         * Let Logger.log() do the filtering since if the level of a
506         * platform logger is altered directly from
507         * java.util.logging.Logger.setLevel(), the levelValue will
508         * not be updated.
509         */
510         void doLog(int level, String msg) {
511             LoggingSupport.log(javaLogger, levelObjects.get(level), msg);
512         }
513 
514         void doLog(int level, String msg, Throwable t) {
515             LoggingSupport.log(javaLogger, levelObjects.get(level), msg, t);
516         }
517 
518         void doLog(int level, String msg, Object... params) {
519             // only pass String objects to the j.u.l.Logger which may
520             // be created by untrusted code
521             int len = (params != null) ? params.length : 0;
522             Object[] sparams = new String[len];
523             for (int i = 0; i < len; i++) {
524                 sparams [i] = String.valueOf(params[i]);
525             }
526             LoggingSupport.log(javaLogger, levelObjects.get(level), msg, sparams);
527         }
528 
529         boolean isEnabled() {
530             Object level = LoggingSupport.getLevel(javaLogger);
531             return level == null || level.equals(levelObjects.get(OFF)) == false;
532         }
533 
534         int getLevel() {
535             Object level = LoggingSupport.getLevel(javaLogger);
536             if (level != null) {
537                 for (Map.Entry<Integer, Object> l : levelObjects.entrySet()) {
538                     if (level == l.getValue()) {
539                         return l.getKey();
540                     }
541                 }
542             }
543             return 0;
544         }
545 
546         void setLevel(int newLevel) {
547             levelValue = newLevel;
548             LoggingSupport.setLevel(javaLogger, levelObjects.get(newLevel));
549         }
550 
551         public boolean isLoggable(int level) {
552             return LoggingSupport.isLoggable(javaLogger, levelObjects.get(level));
553         }
554     }
555 
556     private static String getLevelName(int level) {
557         switch (level) {
558             case OFF     : return "OFF";
559             case SEVERE  : return "SEVERE";
560             case WARNING : return "WARNING";
561             case INFO    : return "INFO";
562             case CONFIG  : return "CONFIG";
563             case FINE    : return "FINE";
564             case FINER   : return "FINER";
565             case FINEST  : return "FINEST";
566             case ALL     : return "ALL";
567             default      : return "UNKNOWN";
568         }
569     }
570 
571 }